package vn.edu.hcmut.cse.ai.search.problems.blocksworld;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import vn.edu.hcmut.cse.ai.search.problems.blocksworld.predicate.BlocksWorldPredicate;
import vn.edu.hcmut.cse.ai.search.problems.blocksworld.predicate.Clear;
import vn.edu.hcmut.cse.ai.search.problems.blocksworld.predicate.Holding;
import vn.edu.hcmut.cse.ai.search.problems.blocksworld.predicate.On;

public class BlocksWorldState {
    private final int numBlocks;
    
    private final HashSet<BlocksWorldPredicate> predicates;
    
    // For optimizing the algorithm
    private final HashSet<Clear> clearPredicates;   
    
    // For optimizing the algorithm
    // Quickly find a block right under another block
    private final HashMap<Integer, Integer> onMap;
    
    // For optimizing the algorithm
    // Store the holding block if it is hold
    private int holdingBlock;
    
    public BlocksWorldState(int numBlocks) {
        this.numBlocks = numBlocks;
        
        this.predicates = new LinkedHashSet<>();
        this.clearPredicates = new LinkedHashSet<>();
        this.onMap = new LinkedHashMap<>();
        this.holdingBlock = -1;
    }
    
    public BlocksWorldState(BlocksWorldState copyState) {
        this.numBlocks = copyState.getNumBlocks();
         
        this.predicates = new LinkedHashSet<>();
        this.predicates.addAll(copyState.predicates);
        
        this.clearPredicates = new LinkedHashSet<>();
        this.clearPredicates.addAll(copyState.clearPredicates);
        
        this.onMap = new LinkedHashMap<>();
        this.onMap.putAll(copyState.onMap);
        
        this.holdingBlock = copyState.getHoldingBlock();
    }

    public int getNumBlocks() {
        return numBlocks;
    }
    
    /**
     * Check if a predicates list is accepted
     * @param predicates
     * @return 
     */
    public boolean isPreconditionListAccepted(List<BlocksWorldPredicate> predicates) {
        return (this.predicates.containsAll(predicates));
    }
    
    /**
     * Check if a predicate is accepted
     * @param predicate
     * @return 
     */
    public boolean isPreconditionAccepted(BlocksWorldPredicate predicate) {
        return this.predicates.contains(predicate);
    }
    
    public void insertPredicates(List<BlocksWorldPredicate> predicates) {
        for (BlocksWorldPredicate pred : predicates) {
            this.predicates.add(pred);
            if (pred.getPredicateType() == BlocksWorldPredicate.PRED_TYPE_CLEAR) {
                this.clearPredicates.add((Clear) pred);
            } else if (pred.getPredicateType() == BlocksWorldPredicate.PRED_TYPE_ON) {
                On onPred = (On)pred;
                this.onMap.put(onPred.getFirstBlock(), onPred.getSecondBlock());
            } else if (pred.getPredicateType() == BlocksWorldPredicate.PRED_TYPE_HOLDING) {
                Holding holdingPred = (Holding)pred;
                this.holdingBlock = holdingPred.getBlock();
            }
        }
    }
    
    public void removePredicates(List<BlocksWorldPredicate> predicates) {
        for (BlocksWorldPredicate pred : predicates) {
            this.predicates.remove(pred);
            if (pred.getPredicateType() == BlocksWorldPredicate.PRED_TYPE_CLEAR) {
                this.clearPredicates.remove((Clear) pred);
            } else if (pred.getPredicateType() == BlocksWorldPredicate.PRED_TYPE_ON) {
                On onPred = (On)pred;
                this.onMap.remove(onPred.getFirstBlock());
            } else if (pred.getPredicateType() == BlocksWorldPredicate.PRED_TYPE_HOLDING) {
                this.holdingBlock = -1; // Not holding any block
            }
        }
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + this.numBlocks;
        hash = 29 * hash + Objects.hashCode(this.predicates);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final BlocksWorldState other = (BlocksWorldState) obj;
        return (this.numBlocks == other.numBlocks) && (this.predicates.equals(other.predicates));
    }

    @Override
    public String toString() {
        return "BlocksWorldState{" + "predicates=" + predicates + '}';
    }    

    public HashSet<BlocksWorldPredicate> getPredicates() {
        return predicates;
    }

    public HashSet<Clear> getClearPredicates() {
        return clearPredicates;
    }   

    public HashMap<Integer, Integer> getOnMap() {
        return onMap;
    }   

    /**
     * Get the holding block.
     * @return -1 if not holding any block or the block number of the hold block
     */
    public int getHoldingBlock() {
        return holdingBlock;
    }
    
    
}
